<?php

/**
 * Trida zapouzdruje praci s webovou sluzbou LastFM pomoci REST
 *
 * Pokud se nekdo zepta, proc trida nededi Zend_Rest_Client, tak je to proto,
 * ze zaba by nemela jezdit na kole a taky sem musel pretizit metodu __call()
 *
 * @see http://www.last.fm/api
 * @author Techi
 */
class LastFM
{

    /**
     * Number of requests where policy will not be applied
     */

    const FREE_REQUESTS = 500;

    /**
     * LastFM REST API gateway
     */

    const URI = 'http://ws.audioscrobbler.com/2.0/';
    
    /**
     *
     * @var Zend_Rest_Client
     */
    private $_client;
    /**
     *
     * @var Zend_Cache_Core
     */
    private $_cache;
   
    /**
     * Last.fm API klíč
     *
     * @var string
     */
    private $_key;
   
    /**
     * Last.fm soukromý klíč
     *
     * @var string
     */
    private $_secret;

    /**
     *
     * @var integer
     */
    private static $_request_count = 0;

    /**
     * Instance se vraci pres getInstance()
     *
     * @param string $key API key
     * @param string $secret
     * @param Zend_Cache_Core $cache - cache to store XML responses
     */
    private function __construct($key, $secret, $cache = null)
    {
        $this->_client = new Zend_Rest_Client();
        $this->_cache = $cache;


        $this->_client->setUri(self::URI);
        

        $http = $this->_client->getHttpClient();

        $http->setMethod(Zend_Http_Client::POST);
        $http->setHeaders('Accept-Charset', 'utf-8');
        $http->setHeaders('User-Agent', 'Ultimate Playlist');
        
        $this->_key = $key;
        $this->_secret = $secret;
    }

    /**
     *
     * @var LastFM
     */
    private static $instance;

    /**
     * Vratime instanci LastFM
     * Singleton pattern
     * API key a Cache je nacten z application.ini
     *
     * @return LastFM
     */
    public static function getInstance()
    {
        if (!isset(self::$instance))
        {
            // pomocne promenne o nasi aplikaci


            // nacteme si konfiguraci a zjistime klic k API

            $config = GlobalConfig::getInstance()->getConfig();
            $api_key = $config->lastfm->app_key;
            $api_secret =  $config->lastfm->app_secret;


            // nechame si vratit instaci cache manageru, ktery udrzuje cache pro lastfm data
            // nastavani cache manageru se dela v application.ini

            $front = Zend_Controller_Front::getInstance();

            $bootstrap = $front->getParam('bootstrap');

            $cacheManager = $bootstrap->bootstrap('CacheManager')
                            ->getResource('CacheManager');

            // pokud se neco posralo v konfiguaci, tak se vyhodi vyjimka

            if (!$cacheManager->hasCacheTemplate('lastfm'))
            {
                throw new LastFM_Exception('cache for lastfm class must be defined in cacheManager');
            }


            $c = __CLASS__;
            self::$instance = new $c($api_key, $api_secret, $cacheManager->getCache('lastfm'));
        }

        return self::$instance;
    }

    /**
     * Cloning not allowed
     */

    private function __clone()
    {

    }

    /**
     *
     * @param string $method
     * @param array $params
     * @return Zend_Rest_Client_Result
     * @todo allow to retrieve outdated cache if LastFM servers are offline
     */
    public function __call($method, $params)
    {
        $params = $params[0];

        $params['api_key'] = $this->_key;
        $params['method'] = str_replace('_', '.', $method);
        
        $tags = array();

        if (isset($params['user']))
        {
            $params['user'] = strtolower($params['user']);
            $tags['user'] = str_replace("-", "_", $params['user']);
        }

        $sign = false;

        if (isset($params['api_sig']))
        {
            $sign = true;
            unset($params['api_sig']);

            $sparams = $params;

            $sparams['rest'] = 1;

            ksort($sparams);

            $str = "";

            foreach ($sparams as $key => $val)
            {
                $str .= $key.$val;
            }

            $str .= $this->_secret;

            $params['api_sig'] = md5($str);
        }

        // sestavení HTTP dotazu z asociativního pole
        $http_params = (http_build_query($params, null, '&'));

        if (is_null($this->_cache))
        {
            $result = $this->_request($http_params);
        }
        else
        {
            $cache_id = md5($http_params);

            $xml = $this->_cache->load($cache_id);

            if (!$xml)
            {
                $result = $this->_request($http_params);

                $this->_archiveRequest($http_params);

                self::$_request_count++;

                $this->_cache->save($result->getIterator()->asXml(), $cache_id, $tags);
            }
            else
            {
                $result = new Zend_Rest_Client_Result($xml);
            }
        }

        return $result;
    }


    /**
     * Archive all lastfm API requests into database
     * Check amount of requests per last 5 minutes
     * After overriding the request limit implies timeout between all request
     *
     *
     * @param string $http_params 
     */
    private function _archiveRequest($http_params)
    {
        $db = Zend_Db_Table::getDefaultAdapter();

        $db->insert('lastfm_request_history', array('inserted' => new Zend_Db_Expr('NOW()'), 'request' => $http_params));

        // tento dotaz zjisti pocet requestu na server za poslednich 5 minut
        $requests = $db->fetchOne("SELECT COUNT(*) FROM lastfm_request_history WHERE inserted > SUBTIME(NOW(), '00:05:00')");

        // first 500 requests per 5 minutes are free of charge :)

        if ($requests > self::FREE_REQUESTS)
        {
            $sleep_wage = ceil(($requests - self::FREE_REQUESTS) / 100);

            // wage is between 1x - 10x
            // 1500 requests per 5 min forces to reduce the timeout to 1 request per second

            usleep(100000 * $sleep_wage); // 0.1 sec * $wage
        }
    }


    /**
     * Call the server
     *
     *
     * @param string $http_params
     * @return Zend_Rest_Client_Result
     */
    private final function _request($http_params)
    {
        $uri = self::URI . '?' . $http_params;

        $this->_client->setUri($uri);
        $this->_client->getHttpClient()->resetParameters(true);
        
        $result = $this->_client->post();
        
        if ($result->error)
        {
            throw new LastFM_Exception($result->error, (int) $result->error['code']);
        }

        return $result;
    }

    /**
     *
     * @param string $user
     * @return Zend_Rest_Client_Result
     */
    public function getUserInfo($user)
    {
        return $this->user_info(array('user' => $user));
    }

    /**
     *
     * @param string $user
     * @return Zend_Rest_Client_Result
     */
    public function getUserFriends($user)
    {
        return $this->user_getfriends(array('user' => $user));
    }

    /**
     *
     * @param string $user
     * @return Zend_Rest_Client_Result
     */
    public function getUserTopArtists($user)
    {
        return $this->user_gettopartists(array('user' => $user));
    }

    /**
     *
     * @param string $user
     * @return Zend_Rest_Client_Result
     */
    public function getUserTopTracks($user, $period = null)
    {
        return $this->user_getTopTracks(array('user' => $user, 'period' => 'overall'));
    }

    /**
     *
     * @param string $user
     * @return Zend_Rest_Client_Result
     */
    public function getUserLovedTracks($user, $limit = null)
    {
        return $this->user_getLovedTracks(array('user' => $user, 'limit' => $limit));
    }

    /**
     *
     * @param string $artist
     * @return Zend_Rest_Client_result
     */
    public function getArtistTopTracks($artist)
    {
        return $this->artist_getTopTracks(array('artist' => $artist));
    }

    /**
     *
     * @param string $user
     * @return Zend_Rest_Client_Result
     */
    public function getUserLastPlayedSongs($user)
    {
        return $this->user_getRecentTracks(array('user' => $user, 'limit' => 50));
    }

    /**
     *
     * @param string $user
     * @param string $artist
     * @return Zend_Rest_Client_Result
     */
    public function getUserTracksByArtist($user,$artist)
    {
        return $this->user_getArtistTracks(array('user' => $user, 'artist' => (string)$artist));
    }
    
    /**
     *
     * @param string $artist
     * @param string $track
     * @return Zend_Rest_Client_Result
     */

    public function getTrackTopTags($artist, $track)
    {
        return $this->track_getTopTags(array('artist' => (string)$artist, 'track' => (string)$track));
    }


    /**
     *
     * @param string $token
     * @return Zend_Rest_Client_Result
     */

    public function getSession($token)
    {
        return $this->auth_getsession(array('token' => (string)$token, "api_sig" => true));
    }



    /**
     * metoda, sloužící ke kontrole jména umělce, vrací nejpravděpodobnější název, případě prázdný řezězec
     *
     * @param string $artist
     * @return Zend_Rest_Client_Result
     */
    public function findArtist($artist)
    {
        $findArtists = $this->artist_search(array('artist' => $artist));
        return $findArtists->name[0];
    }

    /**
     * metoda, sloužící ke kontrole skladby, ze zadaného názvu a umělce vybírá nejpravděpodobnější skladbu
     *
     * @param string $song
     * @param string $artist
     * @return Track
     */
    public function findSong($song, $artist)
    {
        $findSongs = $this->track_search(array('track' => $song, 'artist' => $artist));
        $_artist = (string)$findSongs->artist[0];
        $_song = (string)$findSongs->name[0];
        if ($_song != "" || $_song != null)
        {
            return new Track($_song, $_artist);
        }
        else
        {
            return null;
        }
    }

}
